using ICSharpCode.Decompiler.IL;
using il.lua;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using static il.lua.ConsoleEx;
using Code = Mono.Cecil.Cil.Code;

public class Parser
{
    public static void ParseAssembly(string path)
    {
        var assembly = AssemblyDefinition.ReadAssembly(path, new ReaderParameters()
        {
            ReadingMode = ReadingMode.Deferred,
            ReadSymbols = true,
        });
        var context = new GlobalContext();
        var count = 0;
        foreach (var type in assembly.MainModule.Types)
        {
            if (count > 0)
                CodeLn();
            ParseType(context.GetTypeContext(type));
            count++;
        }
    }

    public static void ParseType(TypeContext context)
    {
        var type = context.Type;
        CommentLn($"{type.FullName} rid=[{type.MetadataToken.RID}]");
        if (type.BaseType != null)
            CodeLn($"local __this__ = class('{type.FullName}','{type.BaseType.FullName}')");
        else
            CodeLn($"local __this__ = class('{type.FullName}')");
        foreach (var method in context.Methods)
        {
            CodeLn();
            ParseMethod(context.GetMethodContext(method));
        }

        CodeLn("return __this__");
    }

    public static void ParseMethod(Context cxt)
    {
        ILFunction function;
        var method = cxt.Method;
        CommentLn(
            $"{cxt.Method.FullName} parameters=[{cxt.Method.Parameters.Count}] rid=[{method.MetadataToken.RID}]");
        if (!method.HasBody)
            return;
        var body = method.Body;
        body.RenameParameters();
        Code("__this__.");
        Code(cxt.MethodName);
        Code("(");
        var count = method.HasThis ? method.Parameters.Count + 1 : method.Parameters.Count;
        for (var i = 0; i < count; i++)
        {
            var parameterName = body.GetParameter(i).Name;
            if (i > 0)
                Code(", ");
            Code(parameterName);
        }
        CodeLn(")");
        Indent++;
        var block = new BlockExpression();
        foreach (var instruction in body.Instructions)
        {
            instruction.Simplify(body);
            //CodeLn($"{instruction.OpCode.StackBehaviourPush} ");
            //CodeLn($"{instruction.OpCode.StackBehaviourPop} ");
            CodeLn($"{instruction} ");
            switch (instruction.OpCode.Code)
            {
                case Code.Ldloc:
                {
                    var name = method.GetVariableName(instruction.Operand as VariableDefinition);
                    cxt.Push(new OperandExpression(name));
                    break;
                }
                case Code.Stloc:
                {
                    var name = method.GetVariableName(instruction.Operand as VariableDefinition);
                    var expression = new AssignExpression(new OperandExpression(name), cxt.Pop());
                    expression.Print();
                    CodeLn();
                    block.Add(expression);
                    break;
                }
                case Code.Ldarg:
                {
                    var name = (instruction.Operand as ParameterDefinition).Name;
                    cxt.Push(new OperandExpression(name));
                    break;
                }
                case Code.Starg:
                {
                    var name = (instruction.Operand as ParameterDefinition).Name;
                    var expression = new AssignExpression(new OperandExpression(name), cxt.Pop());
                    expression.Print();
                    CodeLn();
                    block.Add(expression);
                    break;
                }
                // case Code.Ldfld:
                // {
                //     
                //     break;
                // }
                // case Code.Stfld:
                // {
                //     
                //     break;
                // }
                case Code.Ldc_I4:
                case Code.Ldc_I8:
                case Code.Ldc_R4:
                case Code.Ldc_R8:
                    cxt.Push(new OperandExpression(instruction.Operand));
                    break;
                case Code.Call:
                {
                    var m = instruction.Operand as MethodReference;
                    var argCount = m.HasThis ? m.Parameters.Count + 1 : m.Parameters.Count;
                    var args = new Expression[argCount];
                    for (var i = 0; i < argCount; i++)
                        args[i] = cxt.Pop();
                    var expression = new CallExpression(m, args);
                    if (!m.ReturnType.IsVoid())
                        cxt.Push(expression);
                    else
                    {
                        expression.Print();
                        CodeLn();
                        block.Add(expression);
                    }

                    break;
                }
                case Code.Ret:
                {
                    Expression expression;
                    if (!method.ReturnType.IsVoid())
                        expression = new ReturnExpression(cxt.Pop());
                    else
                        expression = new ReturnExpression(null);
                    expression.Print();
                    CodeLn();
                    block.Add(expression);
                    break;
                }
                case Code.Newarr:
                {
                    Expression expression = new NewArrExpression(instruction.Operand as TypeReference, cxt.Pop());
                    expression.Print();
                    CodeLn();
                    block.Add(expression);
                    break;
                }
                case Code.Stelem_I1:
                {
                    break;
                }
            }

            
        }

        Indent--;
        CommentLn("-------------------------------------------------------");
        block.Print();
        CodeLn("end");
    }
}